/*
 * Scheduler.cpp
 *
 *  Created on: Dec 10, 2014
 *      Author: bohni
 */

#include "Scheduler.h"

Scheduler::Scheduler(Status* systemStatus) {
	status = systemStatus;
	taskArray = 0;
	numberOfTasks = 0;
	checkedTasks = 0;

	maxIdleTime = 0;
	minIdleTime = 0xFFFFFFFF;

	/* initialize timer and interrupts */
	lowLevelInit();
}

void Scheduler::start(Task** tasks, uint8_t taskAmount) {
	taskArray = tasks;
	numberOfTasks = taskAmount;

	initializeTaskDurations();
	/*  Timer start */
	TIM_Cmd(SCHEDULER_TIMER, ENABLE);
}

void Scheduler::pause() {
	/* Timer stop*/
	TIM_Cmd(SCHEDULER_TIMER, DISABLE);
}

void Scheduler::reset() {

	/* Timer stop*/
	TIM_Cmd(SCHEDULER_TIMER, DISABLE);
	/* TODO: Timer reset */

}

void Scheduler::executeTasks() {
	/* TODO:
	 * - behandlung von Timer überlauf
	 *
	 */
	//checkedTasks zur�cksetzen
	checkedTasks = 0;
	// Alle Tasks auf "unchecked" setzen, ausser Tasks mit Prio -1
	for (uint8_t i = 0; i < numberOfTasks; i++) {
		if (taskArray[i]->priority == -1) {
			SET_FLAG(taskArray[i]->statusFlags, FLAG_CHECKED);
			checkedTasks++;
		} else {
			RESET_FLAG(taskArray[i]->statusFlags, FLAG_CHECKED);
		}
	}

	// Schleife läuft so lange durch, bis alle Tasks abgefragt worden sind
	for (uint8_t prio = 0; checkedTasks != numberOfTasks; prio++) {
		for (uint8_t k = 0; k < numberOfTasks; k++) {
			// Wenn Task aktuelle Priorit�t hat und aktiv (erstes Statusbit) ist
			if ((taskArray[k]->priority == prio)
					&& !(GET_FLAG(taskArray[k]->statusFlags, FLAG_CHECKED))) {
				/* increase checkedTasks counter, and set Task as Checked*/
				checkedTasks++;
				SET_FLAG(taskArray[k]->statusFlags, FLAG_CHECKED);
				/* check timer and save starting time of task*/
				uint32_t timerTmp = TIM_GetCounter(SCHEDULER_TIMER);
				/* check time
				 * */
				if (timerTmp > taskArray[k]->maxDuration) {
					/* timeLeft > maxDuration */
					taskArray[k]->update();

					/* update maxDuration */
					if ((timerTmp - TIM_GetCounter(SCHEDULER_TIMER))
							> taskArray[k]->maxDuration) {
						taskArray[k]->maxDuration = timerTmp
								- TIM_GetCounter(SCHEDULER_TIMER);
						/* check task durations */
						checkTaskDurations(k);
					}
				} else {
					/* not enough time to complete task
					 */

					/* increase priority */
					if (taskArray[k]->priority > 1) {
						taskArray[k]->priority--;
					}
				}

			}
		}
	}

	if (TIM_GetCounter(SCHEDULER_TIMER) > maxIdleTime) {
		maxIdleTime = TIM_GetCounter(SCHEDULER_TIMER);
	} else if (TIM_GetCounter(SCHEDULER_TIMER) < minIdleTime) {
		minIdleTime = TIM_GetCounter(SCHEDULER_TIMER);
	}

}
void Scheduler::timerIRQ() {
	/* timer interrupt -> execute Scheduler cycle */

	executeTasks();

}

void Scheduler::lowLevelInit() {
	/* initialize timer and interrupts */
	NVIC_InitTypeDef NVIC_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

	/* TIMER clock enable */
	RCC_APB1PeriphClockCmd(SCHEDULER_TIMER_CLOCK, ENABLE);

	/* Enable the TIM3 gloabal Interrupt */
	NVIC_InitStructure.NVIC_IRQChannel = SCHEDULER_TIMER_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* Time base configuration
	 * Prescaler sets timer clock to 1 MHz -> timings and duration in us
	 * Timer counts down -> timer value = time left
	 * */
	TIM_TimeBaseStructure.TIM_Period =
			(uint32_t) (SCHEDULER_INTERVALL_ms * 1000);
	TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t) (SystemCoreClock / 1000000
			- 1);
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Down;

	TIM_TimeBaseInit(SCHEDULER_TIMER, &TIM_TimeBaseStructure);

	/* TIM Interrupts enable */
	TIM_ITConfig(SCHEDULER_TIMER, TIM_IT_Update, ENABLE);

}
Scheduler::~Scheduler() {
	// TODO Auto-generated destructor stub
}

void Scheduler::checkTaskDurations(uint8_t taskIndex) {
	// Summe aller Ausfuehrdauern von Aufgaben mit Prio 0
	uint32_t timeSum = 0;

	// Zeitdauern aller Tasks mit Prio 0 addieren
	for (uint8_t i = 0; i < numberOfTasks; i++) {
		if (taskArray[i]->priority == 0) {
			timeSum += taskArray[i]->maxDuration;
		}
	}

	// Checken ob uebergebene Task selbst Prio 0 hat
	if (taskArray[taskIndex]->priority == 0) {
		// Falls ja m�ssen alle Faelle ueberprueft werden
		for (uint8_t i = 0; i < numberOfTasks; i++) {
			if (taskArray[i]->priority != 0) {
				// Wenn Prio 0 Tasks zusammen mit einer anderen Task zusammen nicht in eine Periode passen
				if ((timeSum + taskArray[i]->maxDuration)
						>= SCHEDULER_INTERVALL_ms * 1000) {
					// Emergency-Flag setzen
					SET_FLAG(status->globalFlags, EMERGENCY_FLAG);
					break;
				}
			}
		}
	} else {
		// Falls nicht genuegt die Addition der Ausfuehrdauer der uebergebenen Task
		timeSum += taskArray[taskIndex]->maxDuration;

		// Wenn Ausfuehrdauer nicht in Periodendauer passt
		if (timeSum >= SCHEDULER_INTERVALL_ms * 1000) {
			// Emergency-Flag setzen
			SET_FLAG(status->globalFlags, EMERGENCY_FLAG);
		}
	}
}

void Scheduler::initializeTaskDurations() {

	/* Disable Timerinterrupt */
	TIM_ITConfig(SCHEDULER_TIMER, TIM_IT_Update, DISABLE);
	/* Counter Mode up */
	TIM_CounterModeConfig(SCHEDULER_TIMER, TIM_CounterMode_Up);

	uint32_t timeTmp = 0;
	for (uint8_t i = 0; i < numberOfTasks; i++) {
		/* Set Timer */
		TIM_SetCounter(SCHEDULER_TIMER, 0);
		TIM_Cmd(SCHEDULER_TIMER, ENABLE);

		taskArray[i]->update();
		taskArray[i]->maxDuration = TIM_GetCounter(SCHEDULER_TIMER);

		TIM_Cmd(SCHEDULER_TIMER, DISABLE);
	}

	/* Enable Timerinterrupt */
	TIM_ITConfig(SCHEDULER_TIMER, TIM_IT_Update, ENABLE);
	/* Counter Mode Down */
	TIM_CounterModeConfig(SCHEDULER_TIMER, TIM_CounterMode_Down);

}
